﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.IO;
using System.IO.Compression;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using QTPlugin;
using QTPlugin.Interop;
using Kawanon.QtHoney;
using Microsoft.Win32;
using System.Windows.Forms;

namespace Kawanon.Usability
{
	public static class GestureSettings
	{
		private const int ARCHIVE_VERSION = 1;

		[Serializable]
		internal struct ExpressionArchiveHeader
		{
			public int Version { get; set; }
			public int ObjectCount { get; set; }
		}

		// QTTabBar でロード時に BinaryFormatter がアセンブリを特定できない問題の対策（GACがらみ？）
		class WorkaroundSerializationBinder : SerializationBinder
		{
			public override Type BindToType(string assemblyName, string typeName)
			{
				//NOTE: アセンブリから直接タイプ名を取得する
				Type ty = Assembly.GetExecutingAssembly().GetType(typeName);
				//DebugHelpers.Echo(ty +"_ "+ typeName +"_ "+ assemblyName);

				//StringBuilder buf = new StringBuilder();
				//foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies())
				//{
				//    buf.Append(asm.FullName);
				//    buf.Append("\r\n");
				//}
				//DebugHelpers.Echo(buf);
				return ty;
			}
		}

		public static event EventHandler ExpressionsChanged;

		private static List<GestureExpression> s_exprs;

		static GestureSettings()
		{
			var empty = GestureExpression.EmptyTrails;

			s_exprs = new List<GestureExpression>();
			s_exprs.Add(new GestureExpression("L", Action.From(Commands.GoBack, 1)));
			s_exprs.Add(new GestureExpression("R", Action.From(Commands.GoForward, 1)));
			s_exprs.Add(new GestureExpression("DU", Action.From(Commands.GoUpOneLevel)));
			s_exprs.Add(new GestureExpression("UD", Action.From(Commands.RefreshBrowser)));
			s_exprs.Add(new GestureExpression("D", Action.From(Commands.CloseCurrentTab)));
			s_exprs.Add(new GestureExpression(empty, Action.From(Commands.CloseLeft)));
			s_exprs.Add(new GestureExpression(empty, Action.From(Commands.CloseRight)));
			s_exprs.Add(new GestureExpression(empty, Action.From(Commands.CloseAllButCurrent)));
			s_exprs.Add(new GestureExpression(empty, Action.From(Commands.CloseWindow)));
			s_exprs.Add(new GestureExpression(empty, Action.From(Commands.UndoClose)));
			s_exprs.Add(new GestureExpression(empty, Action.From(Commands.ToggleTopMost)));
			//s_exprs.Add(new GestureExpression(empty, Action.From(Commands.FocusFileList)));
			s_exprs.Add(new GestureExpression(empty, Action.From(Commands.OpenTabBarOptionDialog)));
			s_exprs.Add(new GestureExpression(empty, Action.From(Commands.OpenButtonBarOptionDialog)));
			//s_exprs.Add(new GestureExpression(empty, Action.From(Commands.MD5)));
			s_exprs.Add(new GestureExpression("UR", new Actions.NextTab()));
			s_exprs.Add(new GestureExpression("UL", new Actions.PreviousTab()));
			s_exprs.Add(new GestureExpression(empty, new Actions.IsolateTab()));
			s_exprs.Add(new GestureExpression(empty, new Actions.MergeWindows()));
			s_exprs.Add(new GestureExpression(empty, new Actions.ShowGestureSettings()));
		}

		public static GestureExpression FindExpression(IEnumerable<GestureExpression> list, Action action)
		{
			foreach (GestureExpression expr in list)
			{
				if (expr.Action.Equals(action))
				{
					return expr;
				}
			}
			return null;
		}

		public static GestureExpression FindExpression(IEnumerable<GestureExpression> list, int[] trails)
		{
			foreach (GestureExpression expr in list)
			{
				if (expr.TrailsEquals(trails))
				{
					return expr;
				}
			}
			return null;
		}

		public static IList<GestureExpression> GetDefaultExpressions()
		{
			return s_exprs.AsReadOnly();
		}

		public static IList<GestureExpression> GetAvailableExpressions(IList<GestureExpression> src)
		{
			List<GestureExpression> list = new List<GestureExpression>(src);
			list.RemoveAll((Predicate<GestureExpression>)
				delegate(GestureExpression expr)
				{
					if (expr.Trails == null)
					{
						return true;
					}
					else if (expr.Trails.Length == 0)
					{
						return true;
					}
					else if (expr.Action == null)
					{
						return true;
					}
					return false;
				});
			return list;
		}

		public static IList<GestureExpression> OverrideExpressions(IEnumerable<GestureExpression> list, IEnumerable<GestureExpression> src)
		{
			List<GestureExpression> temp = new List<GestureExpression>(src);
			List<GestureExpression> dest = new List<GestureExpression>();
			foreach (GestureExpression expr in list)
			{
				GestureExpression srcExpr = FindExpression(src, expr.Action);
				dest.Add((srcExpr != null) ? srcExpr : expr);
				temp.Remove(srcExpr);
			}
			dest.AddRange(temp);
			return dest;
		}

		public static IList<GestureExpression> LoadAvailableExpressions()
		{
			List<GestureExpression> src = new List<GestureExpression>();
			LoadExpressions(src);
			IList<GestureExpression> list = GestureSettings.OverrideExpressions(
				GestureSettings.GetDefaultExpressions(), src);
			return GestureSettings.GetAvailableExpressions(list);
		}

		public static void LoadExpressions(ICollection<GestureExpression> list)
		{
			RegistryKey key = PluginInformation.OpenRegistryKey(false);
			if (key == null)
			{
				return;
			}

			byte[] bytes = (byte[])key.GetValue("Expressions");
			if (bytes == null)
			{
				return;
			}
			MemoryStream ms = new MemoryStream(bytes);
			LoadExpressions(list, ms);
		}

		public static void LoadExpressions(ICollection<GestureExpression> list, Stream istream)
		{
			DeflateStream inflate = new DeflateStream(istream, CompressionMode.Decompress);
			BinaryFormatter bin = new BinaryFormatter();
			bin.Binder = new WorkaroundSerializationBinder();
			ExpressionArchiveHeader header = (ExpressionArchiveHeader)bin.Deserialize(inflate);
			if (header.Version != ARCHIVE_VERSION)
			{
				throw new ArchiveVersionMismatchException();
			}
			for (int i = 0; i < header.ObjectCount; i++)
			{
				GestureExpression expr = (GestureExpression)bin.Deserialize(inflate);
				list.Add(expr);
			}
		}

		public static void SaveExpressions(ICollection<GestureExpression> list)
		{
			RegistryKey key = PluginInformation.OpenRegistryKey(true);
			MemoryStream ms = new MemoryStream();
			SaveExpressions(list, ms);
			key.SetValue("Expressions", ms.ToArray(), RegistryValueKind.Binary);
			key.Flush();

			if (ExpressionsChanged != null)
			{
				ExpressionsChanged.Invoke(null, new EventArgs());
			}
		}

		public static void SaveExpressions(ICollection<GestureExpression> list, Stream ostream)
		{
			DeflateStream deflate = new DeflateStream(ostream, CompressionMode.Compress, true);
			BinaryFormatter bin = new BinaryFormatter();

			ExpressionArchiveHeader header = new ExpressionArchiveHeader();
			header.Version = ARCHIVE_VERSION;
			header.ObjectCount = list.Count;
			bin.Serialize(deflate, header);

			foreach (GestureExpression expr in list)
			{
				bin.Serialize(deflate, expr);
			}
			deflate.Flush();	// フラッシュして閉じないと読み込みで失敗する
			deflate.Close();	//（エンドマークが書き込まれない）
		}

		public static void DumpExpressions(ICollection<GestureExpression> list)
		{
			MemoryStream ms = new MemoryStream();
			SaveExpressions(list, ms);
			ms.Seek(0, SeekOrigin.Begin);
			DeflateStream inflate = new DeflateStream(ms, CompressionMode.Decompress);
			string dir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().ManifestModule.FullyQualifiedName);
			Stream os = new FileStream(Path.Combine(dir, "expr.bin"), FileMode.Create);
			int c;
			while ((c = inflate.ReadByte()) != -1)
			{
				os.WriteByte((byte)c);
			}
			os.Close();
		}
	}

	public class ArchiveVersionMismatchException : ApplicationException
	{
	}

	public sealed class GestureEventArgs : EventArgs
	{
		public GestureExpression Expression { get; set; }
		public IList<GestureExpression> Suggestions { get; set; }
	}

	public delegate void GestureEventHandler(object sender, GestureEventArgs e);

	[Serializable]
	public abstract class Action
	{
		public class Arguments
		{
			public IPluginServer Server { get; set; }
			public IPluginClient Plugin { get; set; }
		}

		public abstract string Name { get; }

		public abstract void Execute(Arguments args);

		//protected Action(string name)
		//{
		//    Name = name;
		//}

		public override bool Equals(object obj)
		{
			if (!(obj is Action))
			{
				return false;
			}
			if (this.GetType() != obj.GetType())
			{
				return false;
			}
			//NOTE: 名前は編集可能にするかもしれないので
			//GestureAction action = (GestureAction)obj;
			//if (this.Name != action.Name)
			//{
			//    return false;
			//}

			return true;
		}

		public override int GetHashCode()
		{
			return this.GetType().GetHashCode();
		}

		public static Action From(Commands command, object arg)
		{
			return new DefinedCommand(command, arg);
		}

		public static Action From(Commands command)
		{
			return new DefinedCommand(command, null);
		}
	}

	[Serializable]
	internal class DefinedCommand : Action
	{
		private static Dictionary<Commands, string> s_names;

		static DefinedCommand()
		{
			s_names = new Dictionary<Commands, string>();
			s_names.Add(Commands.GoBack, "戻る");
			s_names.Add(Commands.GoForward, "進む");
			s_names.Add(Commands.GoUpOneLevel, "上へ");
			s_names.Add(Commands.RefreshBrowser, "更新");
			s_names.Add(Commands.CloseCurrentTab, "現在のタブを閉じる");
			s_names.Add(Commands.CloseLeft, "左を閉じる");
			s_names.Add(Commands.CloseRight, "右を閉じる");
			s_names.Add(Commands.CloseAllButCurrent, "現在のタブ以外を閉じる");
			s_names.Add(Commands.CloseWindow, "ウィンドウを閉じる");
			s_names.Add(Commands.UndoClose, "閉じたタブを元に戻す");
			s_names.Add(Commands.ToggleTopMost, "最前面表示のON/OFF");
			//s_names.Add(Commands.FocusFileList, "ファイルリストにフォーカス");
			s_names.Add(Commands.OpenTabBarOptionDialog, "オプションを開く");
			s_names.Add(Commands.OpenButtonBarOptionDialog, "ツールバーのカスタマイズ");
			//s_names.Add(Commands.MD5, "MD5ハッシュを計算");
		}

		[NonSerialized]
		private string m_name = null;

		private Commands m_command;
		private object m_arg;

		public override string Name
		{
			get
			{
				if (m_name == null)
				{
					//NOTE: シリアライズ対策のため、ここで初期化
					m_name = s_names[m_command];
				}
				return m_name;
			}
		}

		public DefinedCommand(Commands command, object arg)
		{
			m_command = command;
			m_arg = arg;
		}

		public override void Execute(Arguments args)
		{
			args.Server.ExecuteCommand(m_command, m_arg);
		}

		public override bool Equals(object obj)
		{
			if (!base.Equals(obj))
			{
				return false;
			}

			DefinedCommand action = (DefinedCommand)obj;
			//NOTE: Boxing形は==は使えない
			if (m_command != action.m_command || !Object.Equals(m_arg, action.m_arg))
			{
				return false;
			}
			return true;
		}

		public override int GetHashCode()
		{
			int n = (m_arg != null) ? m_arg.GetHashCode() : 0;
			return (int)(((long)m_command.GetHashCode() + n) / 2);
		}
	}

	[Serializable]
	public class GestureExpression
	{
		public static readonly int[] EmptyTrails = new int[0];

		public virtual string Name
		{
			get
			{
				if (Action != null)
				{
					return Action.Name;
				}
				else
				{
					return null;
				}
			}
		}

		public int[] Trails { get; set; }
		public Action Action { get; set; }
		//public bool Edited { get; set; }

		public GestureExpression(int[] trails, Action action)
		{
			Trails = trails;
			Action = action;
			//Name = action.Name;	// デフォルト名を指定
		}

		public GestureExpression(string urdl, Action action)
			: this(Parse(urdl), action)
		{
		}

		private static int[] Parse(string urdl)
		{
			//if (urdl.Length == 0)
			//{
			//    throw new FormatException("string is empty");
			//}
			int[] trails = Array.ConvertAll(urdl.ToCharArray(), new Converter<char, int>(
				delegate(char c)
				{
					switch (Char.ToUpper(c))
					{
						case 'U':
							return 0;
						case 'R':
							return 1;
						case 'D':
							return 2;
						case 'L':
							return 3;
					}
					throw new FormatException(c + " of " + urdl);
				}));

			return trails;
		}

		public string FormatTrails(string urdl)
		{
			if (Trails == null)
			{
				return String.Empty;
			}
			char[] ar = Array.ConvertAll(Trails, new Converter<int, char>(
				delegate(int direction)
				{
					return urdl[direction];
				}));

			return new String(ar);
		}

		public string FormatTrails()
		{
			return FormatTrails("↑→↓←");
		}

		public bool TrailsEquals(int[] trails)
		{
			if (Trails.Length != trails.Length)
			{
				return false;
			}
			for (int i = 0; i < Trails.Length; i++)
			{
				if (Trails[i] != trails[i])
				{
					return false;
				}
			}
			return true;
		}

		public bool TrailsEquals(GestureExpression expr)
		{
			return TrailsEquals(expr.Trails);
		}

		public override string ToString()
		{
			return String.Format("{0} [{1}]", Name, FormatTrails());
		}
	}
}